#include <stdio.h>
#include <stdlib.h>
#include <libspe.h>
#include <stddef.h>
#include <sys/types.h>
#include <dirent.h>
#include <string.h>
#include <time.h>
#include <math.h>

#define ilspe		6	// ilosc SPE
#define iln			36	// ilosc neuronow
#define dlw			256	// dlugosc wektora neuronu/obrazu

// --- CELL stuff ---

extern spe_program_handle_t spe_fun_handle_learn; // dla spe f-cja uczaca
extern spe_program_handle_t spe_fun_handle_check; // dla spe f-cja rozpoznania

int X;	 		// Szerokosc obrazu
int Y;	 		// Wysokosc obrazu
int R;	 		// Range (zakres/glebia)
int ilSPE; 		// ilosc SPE
int ilN; 		// ilosc neuronow
int dlN;		// dlugosc wektora neuronu
int dlO; 		// dlugosc wektora obrazu
int i,d,j;		// zmienne iteracyjne do petli
int zwyciezca; 	// neuron zwyciezca

// int = 4 bajty, float = 4 bajty
// struktura danych wymienianych miedzy ppe a spe przy obliczaniu odleglosci
typedef struct {
	float obraz[dlw];	// wektor obrazu przesylany do spe
	float neuron[iln/ilspe][dlw];	// wektor neuronu przesylany do spe
	float odleglosc[iln/ilspe];	// odleglosci neuronow od obrazu obliczane i zwracane przez spe
	int padding[2];		// wyrownanie rozmiaru struktury do 16 bajtow
} spe_data;
spe_data speds[6] __attribute__((aligned(16)));

// struktura danych o kandydatach do sasiedztwa
typedef struct {
	int nr;		// numer kandydata
	float val;	// wartosc odleglosci
} kand_data;

FILE *p_obraz;		// wskaznik na plik z obrazem
FILE *p_neuron;		// wskaznik na plik ze zbiorem neuronow
DIR *dp;			// wskaznik na katalog z obrazami do uczenia
struct dirent *ep;  // struktura inf.o katalogu
char tmp[255];
char buf[255];
float *obraz;		// tablica na obraz (w postaci znormalizowanej: 0-1)
float **neurony;	// tablica na wektory neuronow
kand_data *kandydaci;	// lista kandydatow do sasiedztwa
kand_data *kandydaci_odwr;	// odwrocona lista kandydatow do sasiedztwa
int nrl;			// numer lekcji

/*** deklaracja f-cji ***/
void przygotuj_neurony(char *);
void wypisz_neurony();
void wyznacz_zwyciezce();
int porownaj(const void *, const void *);
void wyznacz_sasiedztwo();
void modyfikuj_wagi();
void zapisz_stan_neuronow(char *);
void wczytaj_obraz();
void wypisz_obraz();
void wyslij_do_spe();

int main(int argc, char **argv){

ilSPE	= ilspe;// ilosc SPE
ilN		= iln; 	// ilosc neuronow
dlN		= dlw;	// dlugosc wektora neuronu
dlO		= dlw; 	// dlugosc wektora obrazu
nrl		= 1;	// numer lekcji

/***** UCZENIE *****/

	if(strcmp(argv[1],"-l")==0){

		if(argc >=7){
			if(strcmp(argv[5],"-r")==0){
				przygotuj_neurony(argv[6]); // random ---> neurony[][] ; dorobic manipulacje iloscia neuronow
			} else {exit(1);}
		} else {przygotuj_neurony(NULL);}

		wypisz_neurony();

		/* otwieranie katalogu z obrazami do uczenia */
		dp = opendir (argv[2]);
		if(dp != NULL){
		while(ep = readdir(dp)){
		if(ep->d_type==0x8){ // jezeli to plik
			strcpy(buf,argv[2]);
			printf("Uczenie pliku %s\n",strcat(buf,ep->d_name));
			printf("plik %s:\n",buf);

			wczytaj_obraz(buf); // plik ---> obraz[]

			wypisz_obraz();

			wyslij_do_spe(); // ...i czekaj na odpowiedz; dorobic manipulacje iloscia SPE

			/* ODBIERAMY INFORMACJE O ZWYCIEZCACH */

			wyznacz_zwyciezce(); // spe[1..n] ---> ppe => min()

			wyznacz_sasiedztwo();

			modyfikuj_wagi(nrl,3); // modyfikuje wagi [ktora lekcja, ile lekcji]

			//wypisz_neurony();

			/* czyszczenie pamieci wektora obrazu */
			free(obraz);
			nrl++;

		}} closedir(dp);
		} else { perror ("Nie mozna otworzyc katalogu"); }

		if(argc >=5){
			if(strcmp(argv[3],"-w")==0){
				zapisz_stan_neuronow(argv[4]); // random ---> neurony[][] ; dorobic manipulacje iloscia neuronow
			} else {zapisz_stan_neuronow(NULL);}
		} else {zapisz_stan_neuronow(NULL);}

		/* czyszczenie pamieci wektorow neuronow */
		for(i=0;i<ilN;i++)free(neurony[i]);
		free(neurony);
		free(kandydaci);
		free(kandydaci_odwr);

	}

/***** ROZPOZNANIE *****
 *
 * 	  do napisania!!
 *
 ***********************/

	return 0;
}

/*** definicja f-cji ***/

void przygotuj_neurony(char *r){
    /* dynamiczne allokowanie tablicy ilN neuronow */
    neurony=(float**)malloc((size_t)ilN*sizeof(float*));
    for(i=0;i<ilN;i++)neurony[i]=(float*)malloc((size_t)dlN * sizeof(float));
	/* dynamiczne allokowanie wektora na liste kandydatow */
	kandydaci=(kand_data*)malloc((size_t)ilN*sizeof(kand_data));
	/* dynamiczne allokowanie wektora na odwrocona liste kandydatow */
	kandydaci_odwr=(kand_data*)malloc((size_t)ilN*sizeof(kand_data));
    /* wypelnianie tablicy losowymi znormalizowanymi wagami - szum na starcie*/
    if(r == NULL){
    	srand (time(NULL));
    	for(i=0;i<ilN;i++){
    		for(d=0;d<dlN;d++){
    			neurony[i][d]=((float)(rand()%255))/255.0f; // generowanie szumu
    		}
    	}
    } else {
		/* wczytywanie tablicy neuronow z pliku */
		char tmps[30];
		for(i=0;i<ilN;i++){
			sprintf(tmps,"%sneuron_%d.pgm",r,i);
			p_neuron=fopen(tmps,"r");
			fseek(p_neuron, 0, SEEK_SET);
			fgets(tmp,100,p_neuron);printf("%s",tmp); // P2
			fgets(tmp,100,p_neuron);printf("%s",tmp); // # komentarz
			fscanf(p_neuron,"%s",tmp);X=atoi(tmp);
			fscanf(p_neuron,"%s",tmp);Y=atoi(tmp);printf("%d %d\n",X,Y); // X Y
			fscanf(p_neuron,"%s",tmp);R=atoi(tmp);printf("%d\n",R); // zakres

			/* wypelnianie wektora danymi z pliku obrazu */
			for(d=0;d<dlN;d++){
				fscanf(p_neuron,"%s",tmp);
				neurony[i][d]=((float)atoi(tmp))/(float)R; // normalizowanie
			}
			fclose(p_neuron);
		}
    }
}

void wypisz_neurony(){
    /* wypisywanie tablicy ilN neuronow na ekran */
    for(i=0;i<ilN;i++){
    	printf("- - N[%.2d] - - - - - - - - - -\n",i);
        for(d=0;d<dlN;d++){
            printf("%1.3f ",neurony[i][d]);
    	}
    	printf("\n");
    }
}

void wypisz_obraz(){
    /* wypisywanie wektora obrazu na ekran */
	printf("O: ");
    for(d=0;d<dlO;d++){
            printf("%1.3f ",obraz[d]);
    }printf("\n");
}

void wyznacz_zwyciezce(){
    zwyciezca=0;
    float min_odl=speds[0].odleglosc[0];
    for(i=0;i<ilSPE;i++){
    	for(j=0;j<ilN/ilSPE;j++){
    		kandydaci[(i*ilN/ilSPE)+j].nr=(i*ilN/ilSPE)+j;
    		printf("%1.3f ",speds[i].odleglosc[j]);
    		kandydaci[(i*ilN/ilSPE)+j].val=speds[i].odleglosc[j];
			if(speds[i].odleglosc[j] < min_odl){
				min_odl=speds[i].odleglosc[j];
				zwyciezca=(i*ilN/ilSPE)+j;
			}
    	}
    }
    /* wypisanie na ekran informacji o zwyciezcach */
    printf("Zwyciezca: %d; Odleglosc: %.3f\n", zwyciezca, min_odl);
}

void wyznacz_sasiedztwo(){
	qsort(kandydaci,ilN,sizeof(kand_data),porownaj);
	for(i=0;i<ilN;i++){
		kandydaci_odwr[kandydaci[i].nr].val=kandydaci[i].val;
	    kandydaci_odwr[kandydaci[i].nr].nr=i;
	}
	for(i=0;i<ilN;i++){
		printf("Kandydat[%d] - odl: %.3f\n",kandydaci[i].nr,kandydaci[i].val);
	}
}

int porownaj(const void * a, const void * b){
	kand_data *ia = (kand_data *)a;
	kand_data *ib = (kand_data *)b;
	if(ia->val > ib->val)return 1;
	else if(ia->val == ib->val)return 0;
	else return -1;
}

void modyfikuj_wagi(int it, int itmax){
	//double n=ilN/2;
	//double t=itmax*log10(n/2);
	//double r=(n*pow(M_E,it/t))/2;
	//double D=(double)kandydaci_odwr[i].nr;
	//double N=pow(M_E,(double)(-D/(2*pow(r,2.0f))));
    /* modyfikacja wag wektora neuronu zwyciezcy */
	for(i=0;i<ilN;i++){
		for(d=0;d<dlN;d++){
			neurony[i][d]+=(obraz[d]-neurony[i][d])/sqrt((double)it)/pow(2.0,sqrt((double)kandydaci_odwr[i].nr));
		}
    }
}

void zapisz_stan_neuronow(char *w){
	//zapisanie wektorow neuronow do pliku
	char tmps[30];
	for(i=0;i<ilN;i++){
		sprintf(tmps,"%sneuron_%d.pgm",w,i);
		p_neuron=fopen(tmps,"w");
		fseek(p_neuron, 0, SEEK_SET);
		fputs("P2\n",p_neuron); // P2
		fputs("# plik z wagami\n",p_neuron); // # komentarz
		sprintf(tmps,"%d %d\n%d\n",X,Y,R);
		fputs(tmps,p_neuron);
		// wypelnianie pliku wagami
		for(d=0;d<dlN;d++){
			sprintf(tmps,"%d\n",(int)(neurony[i][d]*255.0f));
			fputs(tmps,p_neuron);
		}
		fclose(p_neuron);
	}
}

void wczytaj_obraz(char *buf){
	/* otworzenie pliku obrazu do czytania */
	p_obraz=fopen(buf,"r");
	fseek(p_obraz, 0, SEEK_SET);
	fgets(tmp,100,p_obraz);printf("%s",tmp); // P2
	fgets(tmp,100,p_obraz);printf("%s",tmp); // # komentarz
	fscanf(p_obraz,"%s",tmp);X=atoi(tmp);
	fscanf(p_obraz,"%s",tmp);Y=atoi(tmp);printf("%d %d\n",X,Y); // X Y
	fscanf(p_obraz,"%s",tmp);R=atoi(tmp);printf("%d\n",R); // zakres

	//dlO=X*Y; <--- z gory ustalone
    /* dynamiczne allokowanie wektora na obraz */
    obraz=(float*)malloc((size_t)dlO*sizeof(float*));
    /* wypelnianie wektora danymi z pliku obrazu */
    for(d=0;d<dlO;d++){
    	fscanf(p_obraz,"%s",tmp);
        obraz[d]=((float)atoi(tmp))/(float)R; // normalizowanie
    }
	fclose(p_obraz);
}

void wyslij_do_spe(){
    /* spakowanie danych do spe_data dla poszczegolnych SPE */
    speid_t spids[ilSPE];
    for(i=0;i<ilSPE;i++){
		for(d=0;d<dlO;d++){
			speds[i].obraz[d]=obraz[d];
		}
		for(j=0;j<ilN/ilSPE;j++){
			for(d=0;d<dlN;d++){
				speds[i].neuron[j][d]=neurony[(i*ilN/ilSPE)+j][d];
			}
		}
    	/* wyslanie paczki do SPE */
		spids[i] = spe_create_thread(0, &spe_fun_handle_learn, &speds[i], NULL, -1, 0);
		if(spids[i] == 0) {
			fprintf(stderr, "Error creating SPE%d thread!\n",i+1);
			exit(1);
		}
	}
	/* czekanie na zakonczenie watkow */
	for(i=0;i<ilSPE;i++){
		spe_wait(spids[i], NULL ,0);
	}
}

